Improve editor fidelity#10
Conversation
…daily-use # Conflicts: # docmostly/Features/Editor/ProseMirrorNode.swift
There was a problem hiding this comment.
2 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+RichBlocks.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+RichBlocks.swift:506">
P2: Require a filename segment before assigning attachmentId. Otherwise `/api/files/manual.pdf` or another one-segment files route is imported as attachmentId `manual.pdf`, corrupting the rich block metadata.</violation>
</file>
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser.swift:130">
P1: `detailsInputRule` checks `text == ":::details "` (trailing space), but on the paste/import path, `block(from:)` trims the line first via `line.trimmingCharacters(in: .whitespaces)`, so `:::details ` becomes `:::details` and never matches.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0def049a76
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
2 issues found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+DocmostInlineHTML.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+DocmostInlineHTML.swift:43">
P2: Literal `<span>` text inside a Docmost comment/mention body prevents finding the closing tag, dropping the preserved comment/mention during Markdown import. Only count real nested span tags in parsed HTML context, or skip Markdown code/text literals before adjusting depth.</violation>
</file>
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+DocmostLinks.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+DocmostLinks.swift:334">
P2: `data-resolved="false"` is treated as resolved because the code checks only for attribute presence. Parse the attribute value so explicit false/unset stays unresolved.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift:197">
P2: New `_` matcher can miss valid single-underscore emphasis spans when preceded by an unmatched repeated delimiter. `delimitedInlineMarkdownMatch` only searches for the first occurrence of the delimiter; if that first `_` is part of `__` and `isPartOfRepeatedDelimiter` returns true, the matcher returns nil instead of continuing to find a later valid `_..._` span. For example, in `__draft _important_` the `_important_` emphasis would be entirely skipped because the first `_` is adjacent to another underscore.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } | ||
|
|
||
| if delimiter == "*", isPartOfStrongDelimiter(openRange, in: markdown) { | ||
| if delimiter.count == 1, isPartOfRepeatedDelimiter(openRange, delimiter: delimiter, in: markdown) { |
There was a problem hiding this comment.
P2: New _ matcher can miss valid single-underscore emphasis spans when preceded by an unmatched repeated delimiter. delimitedInlineMarkdownMatch only searches for the first occurrence of the delimiter; if that first _ is part of __ and isPartOfRepeatedDelimiter returns true, the matcher returns nil instead of continuing to find a later valid _..._ span. For example, in __draft _important_ the _important_ emphasis would be entirely skipped because the first _ is adjacent to another underscore.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift, line 197:
<comment>New `_` matcher can miss valid single-underscore emphasis spans when preceded by an unmatched repeated delimiter. `delimitedInlineMarkdownMatch` only searches for the first occurrence of the delimiter; if that first `_` is part of `__` and `isPartOfRepeatedDelimiter` returns true, the matcher returns nil instead of continuing to find a later valid `_..._` span. For example, in `__draft _important_` the `_important_` emphasis would be entirely skipped because the first `_` is adjacent to another underscore.</comment>
<file context>
@@ -182,7 +194,7 @@ extension NativeEditorMarkdownParser {
}
- if delimiter == "*", isPartOfStrongDelimiter(openRange, in: markdown) {
+ if delimiter.count == 1, isPartOfRepeatedDelimiter(openRange, delimiter: delimiter, in: markdown) {
return nil
}
</file context>
There was a problem hiding this comment.
3 issues found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser.swift:374">
P2: Script/underline formatting is bypassed for runs carrying status, inline math, or mention attributes, risking silent mark loss during Markdown export.</violation>
</file>
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+ScriptUnderline.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+ScriptUnderline.swift:104">
P2: Closing tags are only matched by a literal `</tag>` search, which misses valid HTML end tags with whitespace before `>` (e.g. `</u >`). The opening-tag parser already scans character-by-character and tolerates such whitespace, so this creates an asymmetric parser that can fail on otherwise valid HTML.</violation>
</file>
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift:61">
P2: Raw HTML wrapping doesn't escape the body, risking broken round-trips when user text contains tag-like content</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| runMarkdown = mentionMarkdown(from: mention, fallbackText: runText) | ||
| } else { | ||
| output += inlineRunMarkdown(from: run, text: runText) | ||
| let scriptMarkdown = scriptUnderlineMarkdown( |
There was a problem hiding this comment.
P2: Script/underline formatting is bypassed for runs carrying status, inline math, or mention attributes, risking silent mark loss during Markdown export.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser.swift, line 374:
<comment>Script/underline formatting is bypassed for runs carrying status, inline math, or mention attributes, risking silent mark loss during Markdown export.</comment>
<file context>
@@ -371,10 +371,14 @@ enum NativeEditorMarkdownParser {
runMarkdown = mentionMarkdown(from: mention, fallbackText: runText)
} else {
- let coloredMarkdown = textColorMarkdown(
+ let scriptMarkdown = scriptUnderlineMarkdown(
from: run,
body: inlineRunMarkdown(from: run, text: runText)
</file context>
| var searchStart = bodyStart | ||
|
|
||
| while searchStart < markdown.endIndex, | ||
| let closeRange = markdown[searchStart...].range(of: closingTag, options: .caseInsensitive) { |
There was a problem hiding this comment.
P2: Closing tags are only matched by a literal </tag> search, which misses valid HTML end tags with whitespace before > (e.g. </u >). The opening-tag parser already scans character-by-character and tolerates such whitespace, so this creates an asymmetric parser that can fail on otherwise valid HTML.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser+ScriptUnderline.swift, line 104:
<comment>Closing tags are only matched by a literal `</tag>` search, which misses valid HTML end tags with whitespace before `>` (e.g. `</u >`). The opening-tag parser already scans character-by-character and tolerates such whitespace, so this creates an asymmetric parser that can fail on otherwise valid HTML.</comment>
<file context>
@@ -0,0 +1,166 @@
+ var searchStart = bodyStart
+
+ while searchStart < markdown.endIndex,
+ let closeRange = markdown[searchStart...].range(of: closingTag, options: .caseInsensitive) {
+ guard isInsideMarkdownCodeSpan(closeRange.lowerBound, ranges: codeSpanRanges) == false else {
+ searchStart = closeRange.upperBound
</file context>
| ) -> String { | ||
| var output = body | ||
|
|
||
| if let baselineOffset = run.baselineOffset, baselineOffset != 0 { |
There was a problem hiding this comment.
P2: Raw HTML wrapping doesn't escape the body, risking broken round-trips when user text contains tag-like content
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift, line 61:
<comment>Raw HTML wrapping doesn't escape the body, risking broken round-trips when user text contains tag-like content</comment>
<file context>
@@ -52,6 +52,24 @@ extension NativeEditorMarkdownParser {
+ ) -> String {
+ var output = body
+
+ if let baselineOffset = run.baselineOffset, baselineOffset != 0 {
+ let tagName = baselineOffset > 0 ? "sup" : "sub"
+ output = "<\(tagName)>\(output)</\(tagName)>"
</file context>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostlyTests/Editor/NativeEditorTableMarkdownImportTests.swift">
<violation number="1" location="docmostlyTests/Editor/NativeEditorTableMarkdownImportTests.swift:20">
P2: Use #require instead of #expect for row count to prevent crash on failure</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| return | ||
| } | ||
|
|
||
| #expect(table.rows.count == 2) |
There was a problem hiding this comment.
P2: Use #require instead of #expect for row count to prevent crash on failure
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostlyTests/Editor/NativeEditorTableMarkdownImportTests.swift, line 20:
<comment>Use #require instead of #expect for row count to prevent crash on failure</comment>
<file context>
@@ -0,0 +1,27 @@
+ return
+ }
+
+ #expect(table.rows.count == 2)
+ #expect(table.rows[1].cells[0].plainText == "a | b")
+ #expect(table.rows[1].cells[1].plainText == "Ready")
</file context>
There was a problem hiding this comment.
2 issues found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift:137">
P2: An unmatched earlier backtick run can suppress detection of later valid inline code spans. `codeInlineMarkdownMatch` always starts at `markdown.startIndex` and only tries the first backtick run as an opener. If that run lacks a matching closer of the same length, the function returns nil without reconsidering skipped later runs as new openers. Because `attributedInlineMarkdown` exits its while loop when no match is found, the entire remaining substring is emitted as plain text, swallowing any valid later code spans.</violation>
<violation number="2" location="docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift:140">
P2: Multi-backtick code spans are parsed without Markdown code-span whitespace normalization, so delimiter-padding spaces can become part of the code text.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| let contentStart = markdown.index(after: openIndex) | ||
| let content = String(markdown[contentStart..<closeIndex]) | ||
| let content = String(markdown[openingRun.upperBound..<closingRun.lowerBound]) |
There was a problem hiding this comment.
P2: Multi-backtick code spans are parsed without Markdown code-span whitespace normalization, so delimiter-padding spaces can become part of the code text.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift, line 140:
<comment>Multi-backtick code spans are parsed without Markdown code-span whitespace normalization, so delimiter-padding spaces can become part of the code text.</comment>
<file context>
@@ -134,26 +134,54 @@ extension NativeEditorMarkdownParser {
- let contentStart = markdown.index(after: openIndex)
- let content = String(markdown[contentStart..<closeIndex])
+ let content = String(markdown[openingRun.upperBound..<closingRun.lowerBound])
guard content.isEmpty == false else { return nil }
</file context>
| else { | ||
| return nil | ||
| } | ||
| guard let openingRun = nextBacktickRun(in: markdown, startingAt: markdown.startIndex), |
There was a problem hiding this comment.
P2: An unmatched earlier backtick run can suppress detection of later valid inline code spans. codeInlineMarkdownMatch always starts at markdown.startIndex and only tries the first backtick run as an opener. If that run lacks a matching closer of the same length, the function returns nil without reconsidering skipped later runs as new openers. Because attributedInlineMarkdown exits its while loop when no match is found, the entire remaining substring is emitted as plain text, swallowing any valid later code spans.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser+InlineMarks.swift, line 137:
<comment>An unmatched earlier backtick run can suppress detection of later valid inline code spans. `codeInlineMarkdownMatch` always starts at `markdown.startIndex` and only tries the first backtick run as an opener. If that run lacks a matching closer of the same length, the function returns nil without reconsidering skipped later runs as new openers. Because `attributedInlineMarkdown` exits its while loop when no match is found, the entire remaining substring is emitted as plain text, swallowing any valid later code spans.</comment>
<file context>
@@ -134,26 +134,54 @@ extension NativeEditorMarkdownParser {
- else {
- return nil
- }
+ guard let openingRun = nextBacktickRun(in: markdown, startingAt: markdown.startIndex),
+ let closingRun = nextMatchingBacktickRun(in: markdown, after: openingRun) else { return nil }
</file context>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser.swift:61">
P1: Front-matter removal strips spaces and tabs belonging to the first content line, corrupting list indentation.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| while contentStart < markdown.endIndex, markdown[contentStart].isWhitespace { | ||
| contentStart = markdown.index(after: contentStart) |
There was a problem hiding this comment.
P1: Front-matter removal strips spaces and tabs belonging to the first content line, corrupting list indentation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser.swift, line 61:
<comment>Front-matter removal strips spaces and tabs belonging to the first content line, corrupting list indentation.</comment>
<file context>
@@ -45,6 +46,25 @@ enum NativeEditorMarkdownParser {
+ guard let closeRange = markdown[bodyStart...].range(of: "---") else { return markdown }
+
+ var contentStart = closeRange.upperBound
+ while contentStart < markdown.endIndex, markdown[contentStart].isWhitespace {
+ contentStart = markdown.index(after: contentStart)
+ }
</file context>
| while contentStart < markdown.endIndex, markdown[contentStart].isWhitespace { | |
| contentStart = markdown.index(after: contentStart) | |
| while contentStart < markdown.endIndex, markdown[contentStart].isWhitespace, !markdown[contentStart].isNewline { | |
| contentStart = markdown.index(after: contentStart) | |
| } | |
| while contentStart < markdown.endIndex, markdown[contentStart].isNewline { | |
| contentStart = markdown.index(after: contentStart) | |
| } |
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostly/Features/Editor/NativeEditorMarkdownParser+IframeEmbeds.swift">
<violation number="1" location="docmostly/Features/Editor/NativeEditorMarkdownParser+IframeEmbeds.swift:11">
P2: Inconsistent iframe source validation between HTML and Markdown parsing paths: HTML accepts any `http/https` URL via `isWebURL`, while the Markdown path additionally requires `/embed` or `/live-embed` paths via `isWebEmbedSource`. This asymmetry can break round-trips if a non-embed iframe source parsed from HTML is later represented as a Markdown link and re-imported.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| guard | ||
| let attributes = htmlTagAttributes(from: lines[index], tagName: "iframe"), | ||
| let source = nonEmptyIframeAttribute(attributes["src"]), | ||
| isWebURL(source) |
There was a problem hiding this comment.
P2: Inconsistent iframe source validation between HTML and Markdown parsing paths: HTML accepts any http/https URL via isWebURL, while the Markdown path additionally requires /embed or /live-embed paths via isWebEmbedSource. This asymmetry can break round-trips if a non-embed iframe source parsed from HTML is later represented as a Markdown link and re-imported.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostly/Features/Editor/NativeEditorMarkdownParser+IframeEmbeds.swift, line 11:
<comment>Inconsistent iframe source validation between HTML and Markdown parsing paths: HTML accepts any `http/https` URL via `isWebURL`, while the Markdown path additionally requires `/embed` or `/live-embed` paths via `isWebEmbedSource`. This asymmetry can break round-trips if a non-embed iframe source parsed from HTML is later represented as a Markdown link and re-imported.</comment>
<file context>
@@ -0,0 +1,129 @@
+ guard
+ let attributes = htmlTagAttributes(from: lines[index], tagName: "iframe"),
+ let source = nonEmptyIframeAttribute(attributes["src"]),
+ isWebURL(source)
+ else {
+ return nil
</file context>
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docmostlyTests/Editor/NativeEditorSlashCommandTests.swift">
<violation number="1" location="docmostlyTests/Editor/NativeEditorSlashCommandTests.swift:86">
P3: Duplicate 47-entry title list across two test methods creates maintenance burden and risk of drift</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| @@ -26,6 +26,178 @@ struct NativeEditorSlashCommandTests { | |||
| #expect(titles.contains("Google Sheets")) | |||
There was a problem hiding this comment.
P3: Duplicate 47-entry title list across two test methods creates maintenance burden and risk of drift
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docmostlyTests/Editor/NativeEditorSlashCommandTests.swift, line 86:
<comment>Duplicate 47-entry title list across two test methods creates maintenance burden and risk of drift</comment>
<file context>
@@ -83,6 +83,58 @@ struct NativeEditorSlashCommandTests {
}
}
+ @Test func slashCommandInventoryFollowsDocmostWebMenuOrder() {
+ #expect(slashCommandTitles(for: "") == [
+ "Text",
</file context>
Summary
/tableshape.Validation
28346938478passed on commit941d08f: SwiftLint, Git hygiene, CRDT runtime drift, iPad build, macOS unit tests, and iOS unit tests.git diff --check,xcrun swiftc -parse ..., andswiftlint lint --strict -- ...on the touched parser/test files.Local Xcode builds/tests, simulator launches, and previews were not run because this repo's agent instructions require explicit user approval for local Apple-platform build/test/run commands.